/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2;
import java.io.*;
import java.net.*;
import java.util.*;

/** 
 * Provides facilities for starting Lime II from the command line / another application. 
 * <br>
 * Please note that applications do not really need to use Launcher: they could directly invoke methods of LimeServer
 * (i.e., <code>LimeServer.getServer().boot()</code>, etc..).
 * <br>
 * There are two ways in which Launcher can work: if Launcher runs in the same JVM of the LimeServer it can "directly"
 * communicate with it, otherwise Launcher runs within another JVM and communicates with the LimeServer on the basis
 * of TCP/IP. 
 * <br>
 * Additional information about <u>agents</u> and the <u>agent loading protocol</u> can be found in class LimeServer.
 * <br>
 * We will use the following conventions for the comments of this class: 
 * <P>
 * &#9679 <i>local</i> means this JVM
 * <br>
 * &#9679 <i>remote</i> means a JVM that is different from this one (the one executing Launcher); it can be another
 * JVM on localhost (i.e., a different shell) or a JVM running on another computer 
 * </P>
 * <b>Note About Command <code>-load</code></b> 
 * <br>
 * Please note that if multiple agents are loaded at the same time (i.e. <code>-load A1 A2 A3 ..</code>) nothing can 
 * be said about the "spawn" order of the corresponding threads on the LimeServer, although communication guarantees 
 * the load requests to be delivered to the LimeServer in a FIFO order. 
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini
 */ 

public class Launcher implements PropertyKeys
{
 // the local (= of this JVM) LimeServer singleton 	
 private static LimeServer server = LimeServer.getServer(); 
 // is Launcher invoked from the command line (shell) ? (if not, we don't want Launcher to print messages)
 private static boolean cmd = true;                                
 // error message to be dislayed to the user
 private static String msg = "unknown parsing error";
 
 /**
  * Returns the settings for the <i>local</i> LimeServer.
  * <br><br>
  * <i>local</i> means of this JVM. 
  * <br><br>
  * You have to modify the settings before launching the server, if you want them to be effective. But the settings 
  * should not be modified after that the server has been launched (= booted).
  * 
  * @return Properties (see <code>PropertyKeys</code>)
  */
 public static Properties getServerSettings()
 {
  return LimeServer.settings;	
 }
 
 /**
  * Starts the <i>local</i> LimeServer.
  * <br><br>
  * <i>local</i> means of this JVM. 
  * <br><br>
  * Options for the server are the default ones, unless the application modifies the settings before calling this 
  * method (see <code>getServerSettings</code>).
  */
 public static void launchServer()
 {
  server.boot();	
 }
 
 /**
  * Returns whether the <i>local</i> LimeServer has already been started.
  * <br><br>
  * <i>local</i> means of this JVM.
  * <br><br>
  * If the server has been lauched (= booted) it is fully operative.
  * 
  * @return boolean
  */
 public static boolean serverLaunched()
 {
  return server.isBooted();	
 }
 
 /**
  * Loads an agent into the <i>local</i> LimeServer. 
  * <br><br>
  * <i>local</i> means of this JVM.
  * <br><br>
  * The constructor matching the signature (<code>parameters</code>) is invoked. It must be public.
  * 
  * @param agentname the name of the class of the agent
  * @param parameters array of Objects, the parameters of the constructor you want to call
  * @throws AgentCreationException
  */
 public static void launchAgent(String agentname, Object[] parameters) throws AgentCreationException
 {
  server.loadAgent(agentname, parameters);	
 }
 
 /**
  * Loads an agent into the <i>local</i> LimeServer. 
  * <br><br>
  * <i>local</i> means of this JVM.
  * <br><br>
  * A (public) empty constructor for the agent must be defined (can be implicit). If you defined also non empty 
  * constructors for the class of the agent, since they destroy the implicit empty constructor, you will have to 
  * explicitly define it.
  * <br>
  * An empty constructor does not allow flow of data: the application cannot choose the initial state of the agent.
  *  
  * @param agentname the name of the class of the agent
  * @throws AgentCreationException
  */
 public static void launchAgent(String agentname) throws AgentCreationException
 {
  server.loadAgent(agentname, null);	
 }
 
 /**
  * Loads an agent into <i>local</i> or <i>remote</i> LimeServer. 
  * <br><br>
  * &#9679 <i>local</i> means of this JVM
  * <br>
  * &#9679 <i>remote</i> means of a JVM that is different from this one (the one executing Launcher): it can be another
  * JVM on localhost (i.e., a different shell) or a JVM running on another computer 
  * <br><br>
  * The constructor matching the signature (<code>parameters</code>) is invoked. It must be public. 
  * <br><br>
  * Remote loading is achieved in the basis of TCP/IP: the <u>agent loading protocol</u> is defined in method "run" of
  * the class LimeServer. Please note that: 
  * <P>
  * &#9679 <i>parameters must be Serializable (in fact they are sent to the target LimeServer using TCP/IP) and, 
  * possibly, "small" (few kbytes)</i>
  * <br>
  * &#9679 <i>the bytecode of the class of the agent is not retrieved from the connection (as in the case of true code
  * mobility, i.e. redefinition of the Java ClassLoader). So applications can ask an host only to load agents that 
  * belong to the codebase of the host itself. This enforces a strong security policy: administrators of the server 
  * know the behavior of the agents. Supporting true code mobility would be easy (see example of NetworkClassLoader in
  * ClassLoader javadoc), but many security problems would arise: since agents are just Runnable entities, we should 
  * prevent an agent from being able to execute malicious code on our host</i>
  * </P>
  * 
  * @param agentname the name of the class of the agent
  * @param pars parameters of the constructor you want to call (must be Serializable)
  * @param host the InetAddress of the host of the target LimeServer 
  * @param port the port that the target LimeServer uses for loading agents 
  * @throws IOException
  */
 public static void launchRemoteAgent(String agentname, Serializable[] pars, InetAddress host, int port) throws IOException
 {
  Socket client = new Socket(host, port);
  ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
  oos.writeUTF(agentname);
  if (pars == null) oos.writeInt(0);
  else
  {
   oos.writeInt(pars.length);
   for (int i = 0; i < pars.length; i++) oos.writeObject(pars[i]);
  }
  oos.flush(); 
  oos.close(); 
  client.close();
 } 
 
 /**  
  * Loads an agent into <i>local</i> or <i>remote</i> LimeServer. 
  * <br><br>
  * &#9679 <i>local</i> means of this JVM
  * <br>
  * &#9679 <i>remote</i> means of a JVM that is different from this one (the one executing Launcher): it can be another
  * JVM on localhost (i.e., a different shell) or a JVM running on another computer 
  * <br><br>
  * See comments of <code>launchRemoteAgent(String, Serializable[], InetAddress, int)</code>.
  * 
  * @param agentname the name of the class of the agent
  * @param pars the parameters of the constructor you want to call (must be Serializable)
  * @param host the name or IP of the host of the target LimeServer 
  * @param port the port that the target LimeServer uses for loading agents 
  * @throws IOException
  */
 public static void launchRemoteAgent(String agentname, Serializable[] pars, String host, int port) throws IOException
 {
  launchRemoteAgent(agentname, pars, InetAddress.getByName(host), port);	
 }

 /**
  * Loads an agent into <i>local</i> or <i>remote</i> LimeServer.
  * <br><br>
  * &#9679 <i>local</i> means of this JVM
  * <br>
  * &#9679 <i>remote</i> means of a JVM that is different from this one (the one executing Launcher): it can be another
  * JVM on localhost (i.e., a different shell) or a JVM running on another computer
  * <br><br>
  * A (public) empty constructor for the agent must be defined (can be implicit). If you defined also non empty
  * constructors for the class of the agent, since they destroy the implicit empty constructor, you will have to 
  * explicitly define it.
  * <br>
  * An empty constructor does not allow flow of data: the application cannot choose the initial state of the agent.
  * <br><br>
  * Remote loading is achieved in the basis of TCP/IP: the <u>agent loading protocol</u> is defined in method "run" of
  * the class LimeServer. Please note that:
  * <P>
  * <i>the bytecode of the class of the agent is not retrieved from the connection (as in the case of true code
  * mobility, i.e. redefinition of the Java ClassLoader). So applications can ask an host only to load agents that 
  * belong to the codebase of the host itself. This enforces a strong security policy: administrators of the server 
  * know the behavior of the agents. Supporting true code mobility would be easy (see example of NetworkClassLoader in
  * ClassLoader javadoc), but many security problems would arise: since agents are just Runnable entities, we should 
  * prevent an agent from being able to execute malicious code on our host</i>
  * </P>
  * 
  * @param agentname the name of the class of the agent
  * @param host the InetAddress of the host of the target LimeServer  
  * @param port the port that the target LimeServer uses for loading agents 
  * @throws IOException
  */
 public static void launchRemoteAgent(String agentname, InetAddress host, int port) throws IOException
 {
  launchRemoteAgent(agentname, null, host, port);
 }
 
 /**
  * Loads an agent into <i>local</i> or <i>remote</i> LimeServer.
  * <br><br>
  * &#9679 <i>local</i> means of this JVM
  * <br>
  * &#9679 <i>remote</i> means of a JVM that is different from this one (the one executing Launcher): it can be another
  * JVM on localhost (i.e., a different shell) or a JVM running on another computer
  * <br><br>
  * See comments of <code>launchRemote(String, InetAddress, int)</code>. 
  * 
  * @param agentname the name of the class of the agent
  * @param host the name or IP of the host of the target LimeServer  
  * @param port the port that the target LimeServer uses for loading agents 
  * @throws IOException
  */
 public static void launchRemoteAgent(String agentname, String host, int port) throws IOException
 {
  launchRemoteAgent(agentname, null, InetAddress.getByName(host), port);	
 }
 
 /**
  * Makes LimeServer execute commands (i.e. starting a <i>local</i> LimeServer, loading agent into a <i>remote</i>
  * LimeServer, etc..): see <code>main</code> method.
  * <br><br>
  * &#9679 <i>local</i> means of this JVM
  * <br>
  * &#9679 <i>remote</i> means of a JVM that is different from this one (the one executing Launcher); it can be another
  * JVM on localhost (i.e., a different shell) or a JVM running on another computer 
  * 
  * @param params string of commands, i.e. "-start -port 1980"
  */
 public static void launch(String params)
 {
  cmd = false;	
  
  // parses String into array of String
  StringTokenizer st = new StringTokenizer(params);
  String[] pargs = new String[st.countTokens()];
  int i = 0;
  while (st.hasMoreTokens()) pargs[i++] = st.nextToken();
  
  main(pargs);	
 }
 
 /**
  * Makes a LimeServer execute commands (i.e. starting a <i>local</i> LimeServer, loading agent into a <i>remote</i>
  * LimeServer, etc..): see <code>main</code> method.
  * <br><br>
  * &#9679 <i>local</i> means of this JVM
  * <br>
  * &#9679 <i>remote</i> means of a JVM that is different from this one (the one executing Launcher); it can be another
  * JVM on localhost (i.e., a different shell) or a JVM running on another computer 
  * 
  * @param params array of strings, i.e. "-start", "-port", "1980" 
  */
 public static void launch(String[] params)
 {
  cmd = false;	
  main(params);	
 }
 
 /**
  * Main method: allows to manipulate a LimeServer from the command line. 
  * <br>
  * Since this method is usually run from the shell - it is NOT the case of <code>launch(String)</code> or
  * <code>launch(String[])</code> - the JVM executing this code usually is not the same JVM of the LimeServer: thus 
  * agents are loaded using <code>launchRemoteAgent</code>. 
  * 
  * @param args user input
  */
 public static void main(String[] args) 
 {
  // no args	
  if ((args == null) || (args.length == 0)) { if (cmd) printUsage(); } 
  else
  {	
   // total number of commands
   int total = 13;
   String[] commands = new String[total]; 
   // index of commands
   int VERSION   =  0; commands[VERSION]   = "version";
   int START     =  1; commands[START]     = "start";
   int LOAD      =  2; commands[LOAD]      = "load"; 
   int TSFACTORY =  3; commands[TSFACTORY] = "tsfactory";
   int CA        =  4; commands[CA]        = "ca";							  
   int PORT      =  5; commands[PORT]      = "port";
   int MCASTPORT =  6; commands[MCASTPORT] = "mcastport";
   int MCASTADDR =  7; commands[MCASTADDR] = "mcastaddr";
   int HTTP      =  8; commands[HTTP]      = "http";	
   int TARGET    =  9; commands[TARGET]    = "target";
   int LPORT     = 10; commands[LPORT]     = "lport";
   int ENGAGE    = 11; commands[ENGAGE]    = "engage"; 
   int MSGS      = 12; commands[MSGS]      = "msgs";  
   
   // auxiliary variables 	
   boolean wrong_syntax = false;   
   Vector agents = new Vector();
   int[] already = new int[total]; for (int z = 0; z < already.length; z++) already[z] = 0;
   int[] data = new int[total]; for (int z = 0; z < data.length; z++) data[z] = -1;
	
   // parsing routine: checks unknown commands, duplicated commands, missing values
   // LOAD IS THE ONLY COMMAND THAT CAN BE REPEATED
   int i = 0;
   while (!wrong_syntax && i < args.length)
   {
    int j = 0;	
    boolean found = false;
    while (!wrong_syntax && !found && j < total) if (args[i].equals("-" + commands[j]))
    {	
     if (j != LOAD)
     {
      found = true; 	
      if (already[j] == 0) already[j]++; else { wrong_syntax = true; msg = "duplicated " + commands[j]; }
      if (j == TSFACTORY || j == CA     || j == MCASTADDR || j == MCASTPORT || j == PORT || 
      	  j == HTTP      || j == TARGET || j == LPORT     || j == ENGAGE) 
          if (i + 1 == args.length)    
             { wrong_syntax = true; msg = "missing " + commands[j] + " value"; } else data[j] = ++i;
     } 
   	 else if (j == LOAD)
   	 {	
   	  found = true;	
   	  already[LOAD]++;
   	  int beg = i + 1;	
   	  int k = beg;
   	  while (k < args.length && args[k].charAt(0) != '-') k++;
   	  if (k > beg) 
   	  {
   	   String[] tmp = new String[k - beg];
   	   for (int z = 0; z < k - beg; z++) tmp[z] = args[beg + z];
   	   agents.add(tmp);
   	   i = k - 1;
   	  }
   	  else { wrong_syntax = true; msg = "wrong parameters for load"; }
   	 }
    } else j++;	
    if (!found) { wrong_syntax = true; msg = "unknown command " + args[i]; } 
    i++;
   }
   
   // checks illegal values for parameters 
   for (int j = 0; !wrong_syntax && j < total; j++) 
   if (j == MCASTPORT || j == PORT || j == HTTP || j == LPORT || j == ENGAGE)
   if (already[j] > 0)
   {
   	  if (j == ENGAGE)
   	  {
   	   if (!args[data[j]].equalsIgnoreCase("true") && !args[data[j]].equalsIgnoreCase("false"))	
   	      { wrong_syntax = true; msg = "use <true|false> for " + commands[j]; } 
   	  }	
   	  else
   	  {	
  	   try { Integer.valueOf(args[data[j]]); } 
       catch (NumberFormatException nfe) { wrong_syntax = true; msg = "invalid " + commands[j] + " number"; } 	
   	  }
   }              
            
   // checks for wrong combinations of options and commands
   if (!wrong_syntax) if ((already[TSFACTORY] > 0 || already[ENGAGE] > 0 || already[CA]   > 0  || already[MCASTADDR] > 0 || 
  	                       already[MCASTPORT] > 0 || already[PORT]   > 0 || already[HTTP] > 0  || already[MSGS] > 0) && (already[START] == 0)) 
                         { wrong_syntax = true; msg = "server needs to be started for boot options to have effect"; }
   if (!wrong_syntax) if ((already[TARGET] > 0 || already[LPORT] > 0) && (already[LOAD] == 0))
                         { wrong_syntax = true; msg = "load options can be used only when loading an agent"; }	
   
   // wrong syntax
   if (wrong_syntax) { if (cmd) System.out.println("Invalid: " + msg); } else
   // executes commands
   {	
    if (already[VERSION]   > 0) if (cmd) printVersion(); 	
    if (already[MSGS]      > 0) LimeServer.settings.setProperty(DO_SEEMSG_KEY, "true");
    if (already[TSFACTORY] > 0) LimeServer.settings.setProperty(TSFACTORY_KEY, args[data[TSFACTORY]]);
    if (already[CA]        > 0) LimeServer.settings.setProperty(CAFACTORY_KEY, args[data[CA]]);
    if (already[ENGAGE]    > 0) LimeServer.settings.setProperty(DO_ENGAGE_KEY, args[data[ENGAGE]]);
    if (already[MCASTADDR] > 0) LimeServer.settings.setProperty(MCASTADDR_KEY, args[data[MCASTADDR]]);
    if (already[MCASTPORT] > 0) LimeServer.settings.setProperty(MCASTPORT_KEY, args[data[MCASTPORT]]);
    if (already[PORT]      > 0) LimeServer.settings.setProperty(UCASTPORT_KEY, args[data[PORT]]);
    if (already[HTTP]      > 0) LimeServer.settings.setProperty(HTTP_PORT_KEY, args[data[HTTP]]);
    if (already[START]     > 0) launchServer();
    if (already[LOAD]      > 0) 
       try 
       { 
   	    int def_lport;
   	    InetAddress def_target;  
   	    if (already[LPORT] > 0) def_lport = Integer.parseInt(args[data[LPORT]]);
   	                       // we read the value from settings since the server could be not booted
   	                       else def_lport = Integer.parseInt(LimeServer.settings.getProperty(UCASTPORT_KEY)) + 1; 
   	    if (already[TARGET] > 0) def_target = InetAddress.getByName(args[data[TARGET]]);
                           else def_target = InetAddress.getLocalHost(); 
   	    for (int w = 0; w < agents.size(); w++) 
        {
   	   	 String[] la = (String[]) agents.get(w);
   	   	 String[] pa = null;
   	   	 if (la.length > 1) 
   	   	    { 
   	    	 pa = new String[la.length - 1]; 
   	      	 for (int q = 0; q < la.length - 1; q++) pa[q] = la[q + 1];
   	        }
   	     if (cmd) System.out.print("Loading agent " + la[0] + ".. ");
         launchRemoteAgent(la[0], pa, def_target, def_lport); 
         if (cmd) System.out.println("done");
        }
       } 
       catch (IOException ioe) { if (cmd) { System.out.println("aborted"); ioe.printStackTrace(); } }
   } 
   // end executes commands
  }
  // end no args
 } 
         
 // this method prints usage of Launcher from the command line 
 private static void printUsage()
 {
  System.out.println("Usage:    java lime2.Launcher [M(s)]\n" 
  	               + "M options:                            -- main options\n"
                   + "          -version                       - print version and license\n"
                   + "          -start [B(s)]                  - start runtime on this host\n"
                   + "          -load {RegExpr} [L(s)]         - load agent into runtime\n"
				   + "                                         * multiple -load are allowed\n"
  		           + "B options:                            -- boot options\n"
				   + "          -tsfactory <name>              - choose tuplespace factory\n"
				   + "          -ca <name>                     - choose communication adapter\n"
				   + "          -engage <true|false>           - engage on startup (default true)\n"
				   + "          -http <port>                   - start http diagnostics service\n"
			       + "          -msgs                          - print remote messages\n"
				   + "          -mcastport <port>              - set multicast port\n"
				   + "          -mcastaddr <ip>                - set multicast address\n"
				   + "          -port <port>                   - set port and load port\n"
				   + "                                         * load port is port + 1\n"
				   + "L options:                            -- load options\n"
				   + "          -target <host>                 - select remote server\n"
				   + "          -lport <port>                  - select remote load port\n"
				   + "                                         * L options apply to all -load\n"
				   + "  RegExpr:\n"                             
				   + "          <agent-class> [<agent-arg-1> <agent-arg-2> etc..]");
 }

 // this method prints information about Lime II
 private static void printVersion()
 {
  System.out.println("Lime Middleware v2.02\n"
  		           + "Copyright (C) 2005. Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini\n"
				   + "\n"
				   + "This library is free software; you can redistribute it and/or\n"
				   + "modify it under the terms of the GNU Lesser General Public\n"
				   + "License as published by the Free Software Foundation; either\n"
				   + "version 2.1 of the License, or (at your option) any later version.\n"
				   + "\n"
                   + "This library is distributed in the hope that it will be useful,\n"
				   + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
				   + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
				   + "Lesser General Public License for more details.\n"
				   + "\n"
                   + "You should have received a copy of the GNU Lesser General Public\n"
				   + "License along with this library; if not, write to the Free Software\n"
				   + "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"
				  );
 }
 
}
